#include <Babylon/ScriptLoader.h>
#include <UrlLib/UrlLib.h>
#include <arcana/threading/task.h>
#include "Babylon/DebugTrace.h"

namespace Babylon
{
    class ScriptLoader::Impl
    {
    public:
        Impl(DispatchFunctionT dispatchFunction)
            : m_dispatchFunction{dispatchFunction}
            , m_task{arcana::task_from_result<std::exception_ptr>()}
        {
        }

        void LoadScript(std::string url)
        {
            UrlLib::UrlRequest request;
            DEBUG_TRACE("Loading script at url %s", url.c_str());
            request.Open(UrlLib::UrlMethod::Get, url);
            request.ResponseType(UrlLib::UrlResponseType::String);
            m_task = arcana::when_all(m_task, request.SendAsync()).then(arcana::inline_scheduler, arcana::cancellation::none(), [dispatchFunction = m_dispatchFunction, request = std::move(request), url = std::move(url)](auto) mutable {
                arcana::task_completion_source<void, std::exception_ptr> taskCompletionSource{};
                dispatchFunction([taskCompletionSource, request = std::move(request), url = std::move(url)](Napi::Env env) mutable {
                    DEBUG_TRACE("Evaluating script at url %s", url.c_str());
                    Napi::Eval(env, request.ResponseString().data(), url.data());
                    taskCompletionSource.complete();
                });
                return taskCompletionSource.as_task();
            });
        }

        void Eval(std::string source, std::string url)
        {
            m_task = m_task.then(arcana::inline_scheduler, arcana::cancellation::none(),
                [dispatchFunction = m_dispatchFunction, source = std::move(source), url = std::move(url)](auto) mutable {
                    arcana::task_completion_source<void, std::exception_ptr> taskCompletionSource{};
                    dispatchFunction([taskCompletionSource, source = std::move(source), url = std::move(url)](Napi::Env env) mutable {
                        Napi::Eval(env, source.data(), url.data());
                        taskCompletionSource.complete();
                    });
                    return taskCompletionSource.as_task();
                });
        }

        void Dispatch(std::function<void(Napi::Env)> callback)
        {
            m_task = m_task.then(arcana::inline_scheduler, arcana::cancellation::none(),
                [dispatchFunction = m_dispatchFunction, callback = std::move(callback)](auto) mutable {
                    arcana::task_completion_source<void, std::exception_ptr> taskCompletionSource{};
                    dispatchFunction([taskCompletionSource, callback = std::move(callback)](Napi::Env env) mutable {
                        callback(env);
                        taskCompletionSource.complete();
                    });
                    return taskCompletionSource.as_task();
                });
        }

    private:
        DispatchFunctionT m_dispatchFunction{};
        arcana::task<void, std::exception_ptr> m_task{};
    };

    ScriptLoader::ScriptLoader(DispatchFunctionT dispatchFunction)
        : m_impl{std::make_unique<ScriptLoader::Impl>(std::move(dispatchFunction))}
    {
    }

    ScriptLoader::~ScriptLoader()
    {
    }

    // Move semantics
    ScriptLoader::ScriptLoader(ScriptLoader&&) noexcept = default;
    ScriptLoader& ScriptLoader::operator=(ScriptLoader&&) noexcept = default;

    void ScriptLoader::LoadScript(std::string url)
    {
        m_impl->LoadScript(std::move(url));
    }

    void ScriptLoader::Eval(std::string source, std::string url)
    {
        m_impl->Eval(std::move(source), std::move(url));
    }

    void ScriptLoader::Dispatch(std::function<void BABYLON_API (Napi::Env)> callback)
    {
        m_impl->Dispatch(std::move(callback));
    }
}
